/*
 * ThreadedTaskController.java 29 sept. 2008
 *
 * Sweet Home 3D, Copyright (c) 2008 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.viewcontroller;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

import com.eteks.sweethome3d.model.UserPreferences;

/**
 * A MVC controller used to execute a particular task in a separated thread.
 * @author Emmanuel Puybaret
 */
public class ThreadedTaskController implements Controller
{
	private static ExecutorService tasksExecutor;
	private final UserPreferences preferences;
	private final ViewFactory viewFactory;
	private final Callable<Void> threadedTask;
	private final String taskMessage;
	private final ExceptionHandler exceptionHandler;
	private ThreadedTaskView view;
	private Future<?> task;
	
	/**
	 * Creates a controller that will execute in a separated thread the given task. 
	 * This task shouldn't modify any model objects and should be able to handle
	 * interruptions with <code>Thread</code> methods that the user may provoke 
	 * when he wants to cancel a threaded task. 
	 */
	public ThreadedTaskController(Callable<Void> threadedTask, String taskMessage, ExceptionHandler exceptionHandler,
			UserPreferences preferences, ViewFactory viewFactory)
	{
		this.preferences = preferences;
		this.viewFactory = viewFactory;
		this.threadedTask = threadedTask;
		this.taskMessage = taskMessage;
		this.exceptionHandler = exceptionHandler;
	}
	
	/**
	 * Returns the view controlled by this controller.
	 */
	public ThreadedTaskView getView()
	{
		// Create view lazily only once it's needed
		if (this.view == null)
		{
			this.view = this.viewFactory.createThreadedTaskView(this.taskMessage, this.preferences, this);
		}
		return this.view;
	}
	
	/**
	 * Executes in a separated thread the task given in constructor. This task shouldn't
	 * modify any model objects shared with other threads. 
	 */
	public void executeTask(final View executingView)
	{
		if (tasksExecutor == null)
		{
			tasksExecutor = Executors.newSingleThreadExecutor();
		}
		
		this.task = tasksExecutor.submit(new FutureTask<Void>(this.threadedTask)
		{
			@Override
			public void run()
			{
				// Update running status in view
				getView().invokeLater(new Runnable()
				{
					public void run()
					{
						getView().setTaskRunning(true, executingView);
					}
				});
				super.run();
			}
			
			@Override
			protected void done()
			{
				// Update running status in view
				getView().invokeLater(new Runnable()
				{
					public void run()
					{
						getView().setTaskRunning(false, executingView);
						task = null;
					}
				});
				
				try
				{
					get();
				}
				catch (ExecutionException ex)
				{
					// Handle exceptions with handler            
					final Throwable throwable = ex.getCause();
					if (throwable instanceof Exception)
					{
						getView().invokeLater(new Runnable()
						{
							public void run()
							{
								exceptionHandler.handleException((Exception) throwable);
							}
						});
					}
					else
					{
						throwable.printStackTrace();
					}
				}
				catch (final InterruptedException ex)
				{
					// Handle exception with handler            
					getView().invokeLater(new Runnable()
					{
						public void run()
						{
							exceptionHandler.handleException(ex);
						}
					});
				}
			}
		});
	}
	
	/**
	 * Cancels the threaded task if it's running.
	 */
	public void cancelTask()
	{
		if (this.task != null)
		{
			this.task.cancel(true);
		}
	}
	
	/**
	 * Returns <code>true</code> if the threaded task is running.
	 */
	public boolean isTaskRunning()
	{
		return this.task != null && !this.task.isDone();
	}
	
	/**
	 * Handles exception that may happen during the execution of a threaded task.
	 */
	public static interface ExceptionHandler
	{
		public void handleException(Exception ex);
	}
}
